/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/sunddi.h>
#include <sys/ddi.h>
#include <sys/strlog.h>

#include <inet/common.h>
#include <inet/mib2.h>
#include <inet/ip.h>
#include <inet/ip6.h>

#include <net/pfkeyv2.h>
#include <inet/sadb.h>
#include <inet/ipsec_impl.h>
#include <inet/ipdrop.h>
#include <inet/ipsecesp.h>
#include <inet/ipsecah.h>
#include <sys/kstat.h>

/*
 * Returns B_TRUE if the identities in the SA match the identities
 * in the "latch" structure.
 */

static boolean_t
ipsec_match_outbound_ids(ipsec_latch_t *ipl, ipsa_t *sa)
{
	ASSERT(ipl->ipl_ids_latched == B_TRUE);
	return ipsid_equal(ipl->ipl_local_cid, sa->ipsa_src_cid) &&
	    ipsid_equal(ipl->ipl_remote_cid, sa->ipsa_dst_cid);
}

/* l1 is packet label; l2 is SA label */
boolean_t
ipsec_label_match(ts_label_t *l1, ts_label_t *l2)
{
	if (!is_system_labeled())
		return (B_TRUE);

	/*
	 * Check for NULL label.  Unlabeled SA (l2) always matches;
	 * unlabeled user with labeled  SA always fails
	 */
	if (l2 == NULL)
		return (B_TRUE);

	if (l1 == NULL)
		return (B_FALSE);

	/* Simple IPsec MLS policy: labels must be equal */
	/* In future will need bit in policy saying whether this is the case */

	/*
	 * label_equal() checks DOI and label contents.  We should be
	 * good to go with this check.
	 */
	return (label_equal(l1, l2));
}


/*
 * Look up a security association based on the unique ID generated by IP and
 * transport or tunnel information, such as ports and upper-layer protocol,
 * and the inner and outer address(es).	 Used for uniqueness testing and
 * outbound packets.  The outer source address may be ignored.
 *
 * I expect an SA hash bucket, and that its per-bucket mutex is held.
 * The SA ptr I return will have its reference count incremented by one.
 */
ipsa_t *
ipsec_getassocbyconn(isaf_t *bucket, ip_xmit_attr_t *ixa, uint32_t *src,
    uint32_t *dst, sa_family_t af, uint8_t protocol, ts_label_t *tsl)
{
	ipsa_t *retval, *candidate;
	ipsec_action_t *candact;
	boolean_t need_unique;
	boolean_t tunnel_mode = (ixa->ixa_flags & IXAF_IPSEC_TUNNEL);
	uint64_t unique_id;
	uint32_t old_flags, excludeflags;
	ipsec_policy_t *pp = ixa->ixa_ipsec_policy;
	ipsec_action_t *actlist = ixa->ixa_ipsec_action;
	ipsec_action_t *act;
	ipsec_latch_t *ipl = ixa->ixa_ipsec_latch;
	ipsa_ref_t *ipr = NULL;
	sa_family_t inaf = ixa->ixa_ipsec_inaf;
	uint32_t *insrc = ixa->ixa_ipsec_insrc;
	uint32_t *indst = ixa->ixa_ipsec_indst;
	uint8_t insrcpfx = ixa->ixa_ipsec_insrcpfx;
	uint8_t indstpfx = ixa->ixa_ipsec_indstpfx;

	ASSERT(MUTEX_HELD(&bucket->isaf_lock));

	/*
	 * Caller must set ip_xmit_attr_t structure such that we know
	 * whether this is tunnel mode or transport mode based on
	 * IXAF_IPSEC_TUNNEL.  If this flag is set, we assume that
	 * there are valid inner src and destination addresses to compare.
	 */

	/*
	 * Fast path: do we have a latch structure, is it for this bucket,
	 * and does the generation number match?  If so, refhold and return.
	 */

	if (ipl != NULL) {
		ASSERT((protocol == IPPROTO_AH) || (protocol == IPPROTO_ESP));
		ipr = &ixa->ixa_ipsec_ref[protocol - IPPROTO_ESP];

		retval = ipr->ipsr_sa;

		/*
		 * NOTE: The isaf_gen check (incremented upon
		 * sadb_unlinkassoc()) protects against retval being a freed
		 * SA.  (We're exploiting short-circuit evaluation.)
		 */
		if ((bucket == ipr->ipsr_bucket) &&
		    (bucket->isaf_gen == ipr->ipsr_gen) &&
		    (retval->ipsa_state != IPSA_STATE_DEAD) &&
		    !(retval->ipsa_flags & IPSA_F_CINVALID)) {
			IPSA_REFHOLD(retval);
			return (retval);
		}
	}

	ASSERT((pp != NULL) || (actlist != NULL));
	if (actlist == NULL)
		actlist = pp->ipsp_act;
	ASSERT(actlist != NULL);

	need_unique = actlist->ipa_want_unique;
	unique_id = SA_FORM_UNIQUE_ID(ixa);

	/*
	 * Precompute mask for SA flags comparison: If we need a
	 * unique SA and an SA has already been used, or if the SA has
	 * a unique value which doesn't match, we aren't interested in
	 * the SA..
	 */

	excludeflags = IPSA_F_UNIQUE;
	if (need_unique)
		excludeflags |= IPSA_F_USED;

	/*
	 * Walk the hash bucket, matching on:
	 *
	 * - unique_id
	 * - destination
	 * - source
	 * - algorithms
	 * - inner dst
	 * - inner src
	 * - <MORE TBD>
	 *
	 * Make sure that wildcard sources are inserted at the end of the hash
	 * bucket.
	 *
	 * DEFINITIONS:	A _shared_ SA is one with unique_id == 0 and USED.
	 *		An _unused_ SA is one with unique_id == 0 and not USED.
	 *		A _unique_ SA is one with unique_id != 0 and USED.
	 *		An SA with unique_id != 0 and not USED never happens.
	 */

	candidate = NULL;

	for (retval = bucket->isaf_ipsa; retval != NULL;
	    retval = retval->ipsa_next) {
		ASSERT((candidate == NULL) ||
		    MUTEX_HELD(&candidate->ipsa_lock));

		/*
		 * Q: Should I lock this SA?
		 * A: For now, yes.  I change and use too many fields in here
		 *    (e.g. unique_id) that I may be racing with other threads.
		 *    Also, the refcnt needs to be bumped up.
		 */

		mutex_enter(&retval->ipsa_lock);

		/* My apologies for the use of goto instead of continue. */

		/* Outer destination address */
		if (!IPSA_ARE_ADDR_EQUAL(dst, retval->ipsa_dstaddr, af))
			goto next_ipsa;	/* Destination mismatch. */

		/* Outer source address */
		if (!IPSA_ARE_ADDR_EQUAL(src, retval->ipsa_srcaddr, af) &&
		    !IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af))
			goto next_ipsa;	/* Specific source and not matched. */

		if (tunnel_mode) {
			/* Check tunnel mode */
			if (!(retval->ipsa_flags & IPSA_F_TUNNEL))
				goto next_ipsa; /* Not tunnel mode SA */

			/* Inner destination address */
			if (!IPSA_IS_ADDR_UNSPEC(retval->ipsa_innerdst, inaf)) {
				if (!ip_addr_match((uint8_t *)indst,
				    min(indstpfx, retval->ipsa_innerdstpfx),
				    (in6_addr_t *)retval->ipsa_innerdst))
					goto next_ipsa; /* not matched. */
			}

			/* Inner source address */
			if (!IPSA_IS_ADDR_UNSPEC(retval->ipsa_innersrc, inaf)) {
				if (!ip_addr_match((uint8_t *)insrc,
				    min(insrcpfx, retval->ipsa_innersrcpfx),
				    (in6_addr_t *)retval->ipsa_innersrc))
					goto next_ipsa; /* not matched. */
			}
		} else {
			/* Check transport mode */
			if (retval->ipsa_flags & IPSA_F_TUNNEL)
				goto next_ipsa; /* Not transport mode SA */

			/*
			 * TODO - If we ever do RFC 3884's dream of transport-
			 * mode SAs with inner IP address selectors, we need
			 * to put some code here.
			 */
		}

		/*
		 * XXX should be able to use cached/latched action
		 * to dodge this loop
		 */
		for (act = actlist; act != NULL; act = act->ipa_next) {
			ipsec_act_t *ap = &act->ipa_act;
			if (ap->ipa_type != IPSEC_POLICY_APPLY)
				continue;

			/*
			 * XXX ugly.  should be better way to do this test
			 */
			if (protocol == IPPROTO_AH) {
				if (!(ap->ipa_apply.ipp_use_ah))
					continue;
				if (ap->ipa_apply.ipp_auth_alg !=
				    retval->ipsa_auth_alg)
					continue;
				if (ap->ipa_apply.ipp_ah_minbits >
				    retval->ipsa_authkeybits)
					continue;
			} else {
				if (!(ap->ipa_apply.ipp_use_esp))
					continue;

				if ((ap->ipa_apply.ipp_encr_alg !=
				    retval->ipsa_encr_alg))
					continue;

				if (ap->ipa_apply.ipp_espe_minbits >
				    retval->ipsa_encrkeybits)
					continue;

				if (ap->ipa_apply.ipp_esp_auth_alg != 0) {
					if (ap->ipa_apply.ipp_esp_auth_alg !=
					    retval->ipsa_auth_alg)
						continue;
					if (ap->ipa_apply.ipp_espa_minbits >
					    retval->ipsa_authkeybits)
						continue;
				}
			}

			/*
			 * Check key mgmt proto, cookie
			 */
			if ((ap->ipa_apply.ipp_km_proto != 0) &&
			    (retval->ipsa_kmp != 0) &&
			    (ap->ipa_apply.ipp_km_proto != retval->ipsa_kmp))
				continue;

			if ((ap->ipa_apply.ipp_km_cookie != 0) &&
			    (retval->ipsa_kmc != 0) &&
			    (ap->ipa_apply.ipp_km_cookie != retval->ipsa_kmc))
				continue;

			break;
		}
		if (act == NULL)
			goto next_ipsa;	/* nothing matched */

		/*
		 * Do identities match?
		 */
		if (ipl && ipl->ipl_ids_latched &&
		    !ipsec_match_outbound_ids(ipl, retval))
			goto next_ipsa;

		/*
		 * Do labels match?
		 */
		if (!ipsec_label_match(tsl, retval->ipsa_tsl))
			goto next_ipsa;

		/*
		 * At this point, we know that we have at least a match on:
		 *
		 * - dest
		 * - source (if source is specified, i.e. non-zeroes)
		 * - inner dest (if specified)
		 * - inner source (if specified)
		 * - auth alg (if auth alg is specified, i.e. non-zero)
		 * - encrypt. alg (if encrypt. alg is specified, i.e. non-zero)
		 * and we know that the SA keylengths are appropriate.
		 *
		 * (Keep in mind known-src SAs are hit before zero-src SAs,
		 * thanks to sadb_insertassoc().)
		 * If we need a unique asssociation, optimally we have
		 * ipsa_unique_id == unique_id, otherwise NOT USED
		 * is held in reserve (stored in candidate).
		 *
		 * For those stored in candidate, take best-match (i.e. given
		 * a choice, candidate should have non-zero ipsa_src).
		 */

		/*
		 * If SA has a unique value which matches, we're all set...
		 * "key management knows best"
		 */
		if ((retval->ipsa_flags & IPSA_F_UNIQUE) &&
		    ((unique_id & retval->ipsa_unique_mask) ==
		    retval->ipsa_unique_id))
			break;

		/*
		 * If we need a unique SA and this SA has already been used,
		 * or if the SA has a unique value which doesn't match,
		 * this isn't for us.
		 */

		if (retval->ipsa_flags & excludeflags)
			goto next_ipsa;


		/*
		 * I found a candidate..
		 */
		if (candidate == NULL) {
			/*
			 * and didn't already have one..
			 */
			candidate = retval;
			candact = act;
			continue;
		} else {
			/*
			 * If candidate's source address is zero and
			 * the current match (i.e. retval) address is
			 * not zero, we have a better candidate..
			 */
			if (IPSA_IS_ADDR_UNSPEC(candidate->ipsa_srcaddr, af) &&
			    !IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af)) {
				mutex_exit(&candidate->ipsa_lock);
				candidate = retval;
				candact = act;
				continue;
			}
		}
next_ipsa:
		mutex_exit(&retval->ipsa_lock);
	}
	ASSERT((retval == NULL) || MUTEX_HELD(&retval->ipsa_lock));
	ASSERT((candidate == NULL) || MUTEX_HELD(&candidate->ipsa_lock));
	ASSERT((retval == NULL) || (act != NULL));
	ASSERT((candidate == NULL) || (candact != NULL));

	/* Let caller react to a lookup failure when it gets NULL. */
	if (retval == NULL && candidate == NULL)
		return (NULL);

	if (retval == NULL) {
		ASSERT(MUTEX_HELD(&candidate->ipsa_lock));
		retval = candidate;
		act = candact;
	} else if (candidate != NULL) {
		mutex_exit(&candidate->ipsa_lock);
	}
	ASSERT(MUTEX_HELD(&retval->ipsa_lock));
	ASSERT(act != NULL);

	/*
	 * Even though I hold the mutex, since the reference counter is an
	 * atomic operation, I really have to use the IPSA_REFHOLD macro.
	 */
	IPSA_REFHOLD(retval);

	/*
	 * This association is no longer unused.
	 */
	old_flags = retval->ipsa_flags;
	retval->ipsa_flags |= IPSA_F_USED;

	/*
	 * Cache a reference to this SA for the fast path.
	 */
	if (ipr != NULL) {
		ipr->ipsr_bucket = bucket;
		ipr->ipsr_gen = bucket->isaf_gen;
		ipr->ipsr_sa = retval;
		/* I'm now caching, so the cache-invalid flag goes away! */
		retval->ipsa_flags &= ~IPSA_F_CINVALID;
	}
	/*
	 * Latch various things while we're here..
	 */
	if (ipl != NULL) {
		if (!ipl->ipl_ids_latched) {
			ipsec_latch_ids(ipl,
			    retval->ipsa_src_cid, retval->ipsa_dst_cid);
		}
		if (ixa->ixa_ipsec_action == NULL) {
			IPACT_REFHOLD(act);
			ixa->ixa_ipsec_action = act;
		}
	}

	/*
	 * Set the uniqueness only first time.
	 */
	if (need_unique && !(old_flags & IPSA_F_USED)) {
		if (retval->ipsa_unique_id == 0) {
			ASSERT((retval->ipsa_flags & IPSA_F_UNIQUE) == 0);
			/*
			 * From now on, only this src, dst[ports, addr],
			 * proto, should use it.
			 */
			retval->ipsa_flags |= IPSA_F_UNIQUE;
			retval->ipsa_unique_id = unique_id;
			retval->ipsa_unique_mask = SA_UNIQUE_MASK(
			    ixa->ixa_ipsec_src_port, ixa->ixa_ipsec_dst_port,
			    protocol, 0);
		}

		/*
		 * Set the source address and adjust the hash
		 * buckets only if src_addr is zero.
		 */
		if (IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af)) {
			/*
			 * sadb_unlinkassoc() will decrement the refcnt.  Bump
			 * up when we have the lock so that we don't have to
			 * acquire locks when we come back from
			 * sadb_insertassoc().
			 *
			 * We don't need to bump the bucket's gen since
			 * we aren't moving to a new bucket.
			 */
			IPSA_REFHOLD(retval);
			IPSA_COPY_ADDR(retval->ipsa_srcaddr, src, af);
			mutex_exit(&retval->ipsa_lock);
			sadb_unlinkassoc(retval);
			/*
			 * Since the bucket lock is held, we know
			 * sadb_insertassoc() will succeed.
			 */
#ifdef DEBUG
			if (sadb_insertassoc(retval, bucket) != 0) {
				cmn_err(CE_PANIC,
				    "sadb_insertassoc() failed in "
				    "ipsec_getassocbyconn().\n");
			}
#else	/* non-DEBUG */
			(void) sadb_insertassoc(retval, bucket);
#endif	/* DEBUG */
			return (retval);
		}
	}
	mutex_exit(&retval->ipsa_lock);

	return (retval);
}

/*
 * Look up a security association based on the security parameters index (SPI)
 * and address(es).  This is used for inbound packets and general SA lookups
 * (even in outbound SA tables).  The source address may be ignored.  Return
 * NULL if no association is available.	 If an SA is found, return it, with
 * its refcnt incremented.  The caller must REFRELE after using the SA.
 * The hash bucket must be locked down before calling.
 */
ipsa_t *
ipsec_getassocbyspi(isaf_t *bucket, uint32_t spi, uint32_t *src, uint32_t *dst,
    sa_family_t af)
{
	ipsa_t *retval;

	ASSERT(MUTEX_HELD(&bucket->isaf_lock));

	/*
	 * Walk the hash bucket, matching exactly on SPI, then destination,
	 * then source.
	 *
	 * Per-SA locking doesn't need to happen, because I'm only matching
	 * on addresses.  Addresses are only changed during insertion/deletion
	 * from the hash bucket.  Since the hash bucket lock is held, we don't
	 * need to worry about addresses changing.
	 */

	for (retval = bucket->isaf_ipsa; retval != NULL;
	    retval = retval->ipsa_next) {
		if (retval->ipsa_spi != spi)
			continue;
		if (!IPSA_ARE_ADDR_EQUAL(dst, retval->ipsa_dstaddr, af))
			continue;

		/*
		 * Assume that wildcard source addresses are inserted at the
		 * end of the hash bucket.  (See sadb_insertassoc().)
		 * The following check for source addresses is a weak form
		 * of access control/source identity verification.  If an
		 * SA has a source address, I only match an all-zeroes
		 * source address, or that particular one.  If the SA has
		 * an all-zeroes source, then I match regardless.
		 *
		 * There is a weakness here in that a packet with all-zeroes
		 * for an address will match regardless of the source address
		 * stored in the packet.
		 *
		 * Note that port-level packet selectors, if present,
		 * are checked in ipsec_check_ipsecin_unique().
		 */
		if (IPSA_ARE_ADDR_EQUAL(src, retval->ipsa_srcaddr, af) ||
		    IPSA_IS_ADDR_UNSPEC(retval->ipsa_srcaddr, af) ||
		    IPSA_IS_ADDR_UNSPEC(src, af))
			break;
	}

	if (retval != NULL) {
		/*
		 * Just refhold the return value.  The caller will then
		 * make the appropriate calls to set the USED flag.
		 */
		IPSA_REFHOLD(retval);
	}

	return (retval);
}

boolean_t
ipsec_outbound_sa(mblk_t *data_mp, ip_xmit_attr_t *ixa, uint_t proto)
{
	ipaddr_t dst;
	uint32_t *dst_ptr, *src_ptr;
	isaf_t *bucket;
	ipsa_t *assoc;
	ip_pkt_t ipp;
	in6_addr_t dst6;
	ipsa_t **sa;
	sadbp_t *sadbp;
	sadb_t *sp;
	sa_family_t af;
	ip_stack_t	*ipst = ixa->ixa_ipst;
	netstack_t	*ns = ipst->ips_netstack;

	ASSERT(ixa->ixa_flags & IXAF_IPSEC_SECURE);

	if (proto == IPPROTO_ESP) {
		ipsecesp_stack_t	*espstack;

		espstack = ns->netstack_ipsecesp;
		sa = &ixa->ixa_ipsec_esp_sa;
		sadbp = &espstack->esp_sadb;
	} else {
		ipsecah_stack_t	*ahstack;

		ASSERT(proto == IPPROTO_AH);
		ahstack = ns->netstack_ipsecah;
		sa = &ixa->ixa_ipsec_ah_sa;
		sadbp = &ahstack->ah_sadb;
	}

	ASSERT(*sa == NULL);

	if (ixa->ixa_flags & IXAF_IS_IPV4) {
		ipha_t *ipha = (ipha_t *)data_mp->b_rptr;

		ASSERT(IPH_HDR_VERSION(ipha) == IPV4_VERSION);
		dst = ip_get_dst(ipha);
		sp = &sadbp->s_v4;
		af = AF_INET;

		/*
		 * NOTE:Getting the outbound association is considerably
		 *	painful.  ipsec_getassocbyconn() will require more
		 *	parameters as policy implementations mature.
		 */
		bucket = OUTBOUND_BUCKET_V4(sp, dst);
		src_ptr = (uint32_t *)&ipha->ipha_src;
		dst_ptr = (uint32_t *)&dst;
	} else {
		ip6_t *ip6h = (ip6_t *)data_mp->b_rptr;

		ASSERT(IPH_HDR_VERSION(ip6h) == IPV6_VERSION);
		dst6 = ip_get_dst_v6(ip6h, data_mp, NULL);
		af = AF_INET6;

		bzero(&ipp, sizeof (ipp));
		sp = &sadbp->s_v6;

		/* Same NOTE: applies here! */
		bucket = OUTBOUND_BUCKET_V6(sp, dst6);
		src_ptr = (uint32_t *)&ip6h->ip6_src;
		dst_ptr = (uint32_t *)&dst6;
	}

	mutex_enter(&bucket->isaf_lock);
	assoc = ipsec_getassocbyconn(bucket, ixa, src_ptr, dst_ptr, af,
	    proto, ixa->ixa_tsl);
	mutex_exit(&bucket->isaf_lock);

	if (assoc == NULL)
		return (B_FALSE);

	if (assoc->ipsa_state == IPSA_STATE_DEAD) {
		IPSA_REFRELE(assoc);
		return (B_FALSE);
	}

	ASSERT(assoc->ipsa_state != IPSA_STATE_LARVAL);

	*sa = assoc;
	return (B_TRUE);
}

/*
 * Inbound IPsec SA selection.
 * Can return a pulled up mblk.
 * When it returns non-NULL ahp is updated
 */
mblk_t *
ipsec_inbound_ah_sa(mblk_t *mp, ip_recv_attr_t *ira, ah_t **ahp)
{
	ipha_t *ipha;
	ipsa_t 	*assoc;
	ah_t *ah;
	isaf_t *hptr;
	boolean_t isv6;
	ip6_t *ip6h;
	int ah_offset;
	uint32_t *src_ptr, *dst_ptr;
	int pullup_len;
	sadb_t *sp;
	sa_family_t af;
	netstack_t	*ns = ira->ira_ill->ill_ipst->ips_netstack;
	ipsec_stack_t	*ipss = ns->netstack_ipsec;
	ipsecah_stack_t	*ahstack = ns->netstack_ipsecah;

	IP_AH_BUMP_STAT(ipss, in_requests);

	isv6 = !(ira->ira_flags & IRAF_IS_IPV4);
	if (isv6) {
		ip6h = (ip6_t *)mp->b_rptr;
		ah_offset = ipsec_ah_get_hdr_size_v6(mp, B_TRUE);
	} else {
		ipha = (ipha_t *)mp->b_rptr;
		ASSERT(ipha->ipha_protocol == IPPROTO_AH);
		ah_offset = ipha->ipha_version_and_hdr_length -
		    (uint8_t)((IP_VERSION << 4));
		ah_offset <<= 2;
	}

	/*
	 * We assume that the IP header is pulled up until
	 * the options. We need to see whether we have the
	 * AH header in the same mblk or not.
	 */
	pullup_len = ah_offset + sizeof (ah_t);
	if (mp->b_rptr + pullup_len > mp->b_wptr) {
		if (!pullupmsg(mp, pullup_len)) {
			ipsec_rl_strlog(ns, ip_mod_info.mi_idnum, 0, 0,
			    SL_WARN | SL_ERROR,
			    "ipsec_inbound_ah_sa: Small AH header\n");
			IP_AH_BUMP_STAT(ipss, in_discards);
			ip_drop_packet(mp, B_TRUE, ira->ira_ill,
			    DROPPER(ipss, ipds_ah_bad_length),
			    &ipss->ipsec_dropper);
			return (NULL);
		}
		if (isv6)
			ip6h = (ip6_t *)mp->b_rptr;
		else
			ipha = (ipha_t *)mp->b_rptr;
	}

	ah = (ah_t *)(mp->b_rptr + ah_offset);

	if (isv6) {
		src_ptr = (uint32_t *)&ip6h->ip6_src;
		dst_ptr = (uint32_t *)&ip6h->ip6_dst;
		sp = &ahstack->ah_sadb.s_v6;
		af = AF_INET6;
	} else {
		src_ptr = (uint32_t *)&ipha->ipha_src;
		dst_ptr = (uint32_t *)&ipha->ipha_dst;
		sp = &ahstack->ah_sadb.s_v4;
		af = AF_INET;
	}

	hptr = INBOUND_BUCKET(sp, ah->ah_spi);
	mutex_enter(&hptr->isaf_lock);
	assoc = ipsec_getassocbyspi(hptr, ah->ah_spi, src_ptr, dst_ptr, af);
	mutex_exit(&hptr->isaf_lock);

	if (assoc == NULL || assoc->ipsa_state == IPSA_STATE_DEAD ||
	    assoc->ipsa_state == IPSA_STATE_ACTIVE_ELSEWHERE) {
		IP_AH_BUMP_STAT(ipss, lookup_failure);
		IP_AH_BUMP_STAT(ipss, in_discards);
		ipsecah_in_assocfailure(mp, 0,
		    SL_ERROR | SL_CONSOLE | SL_WARN,
		    "ipsec_inbound_ah_sa: No association found for "
		    "spi 0x%x, dst addr %s\n",
		    ah->ah_spi, dst_ptr, af, ira);
		if (assoc != NULL) {
			IPSA_REFRELE(assoc);
		}
		return (NULL);
	}

	if (assoc->ipsa_state == IPSA_STATE_LARVAL) {
		/* Not fully baked; swap the packet under a rock until then */

		mp = sadb_set_lpkt(assoc, mp, ira);
		if (mp == NULL) {
			IPSA_REFRELE(assoc);
			return (NULL);
		}
		/* Looks like the SA is no longer LARVAL. */
	}

	/* Are the IPsec fields initialized at all? */
	if (!(ira->ira_flags & IRAF_IPSEC_SECURE)) {
		ira->ira_ipsec_action = NULL;
		ira->ira_ipsec_ah_sa = NULL;
		ira->ira_ipsec_esp_sa = NULL;
	}

	/*
	 * Save a reference to the association so that it can
	 * be retrieved after execution. We free any AH SA reference
	 * already there (innermost SA "wins". The reference to
	 * the SA will also be used later when doing the policy checks.
	 */
	if (ira->ira_ipsec_ah_sa != NULL) {
		IPSA_REFRELE(ira->ira_ipsec_ah_sa);
	}
	ira->ira_flags |= IRAF_IPSEC_SECURE;
	ira->ira_ipsec_ah_sa = assoc;

	*ahp = ah;
	return (mp);
}

/*
 * Can return a pulled up mblk.
 * When it returns non-NULL esphp is updated
 */
mblk_t *
ipsec_inbound_esp_sa(mblk_t *data_mp, ip_recv_attr_t *ira, esph_t **esphp)
{
	mblk_t *placeholder;
	uint32_t *src_ptr, *dst_ptr;
	ipha_t *ipha;
	ip6_t *ip6h;
	esph_t *esph;
	ipsa_t *ipsa;
	isaf_t *bucket;
	uint_t preamble;
	sa_family_t af;
	boolean_t isv6;
	sadb_t *sp;
	netstack_t	*ns = ira->ira_ill->ill_ipst->ips_netstack;
	ipsec_stack_t	*ipss = ns->netstack_ipsec;
	ipsecesp_stack_t *espstack = ns->netstack_ipsecesp;

	IP_ESP_BUMP_STAT(ipss, in_requests);

	isv6 = !(ira->ira_flags & IRAF_IS_IPV4);
	if (isv6) {
		ip6h = (ip6_t *)data_mp->b_rptr;
	} else {
		ipha = (ipha_t *)data_mp->b_rptr;
	}

	/*
	 * Put all data into one mblk if it's not there already.
	 * XXX This is probably bad long-term.  Figure out better ways of doing
	 * this.  Much of the inbound path depends on all of the data being
	 * in one mblk.
	 *
	 * XXX Jumbogram issues will have to be dealt with here.
	 * If the plen is 0, we'll have to scan for a HBH header with the
	 * actual packet length.
	 */
	if (data_mp->b_datap->db_ref > 1 ||
	    (data_mp->b_wptr - data_mp->b_rptr) < ira->ira_pktlen) {
		placeholder = msgpullup(data_mp, -1);
		if (placeholder == NULL) {
			IP_ESP_BUMP_STAT(ipss, in_discards);
			ip_drop_packet(data_mp, B_TRUE, ira->ira_ill,
			    DROPPER(ipss, ipds_esp_nomem),
			    &ipss->ipsec_dropper);
			return (NULL);
		} else {
			/* Reset packet with new pulled up mblk. */
			freemsg(data_mp);
			data_mp = placeholder;
		}
	}

	/*
	 * Find the ESP header, point the address pointers at the appropriate
	 * IPv4/IPv6 places.
	 */
	if (isv6) {
		ip6h = (ip6_t *)data_mp->b_rptr;
		src_ptr = (uint32_t *)&ip6h->ip6_src;
		dst_ptr = (uint32_t *)&ip6h->ip6_dst;
		if (ip6h->ip6_nxt != IPPROTO_ESP) {
			/* There are options that need to be processed. */
			preamble = ip_hdr_length_v6(data_mp, ip6h);
		} else {
			preamble = sizeof (ip6_t);
		}

		sp = &espstack->esp_sadb.s_v6;
		af = AF_INET6;
	} else {
		ipha = (ipha_t *)data_mp->b_rptr;
		src_ptr = (uint32_t *)&ipha->ipha_src;
		dst_ptr = (uint32_t *)&ipha->ipha_dst;
		preamble = IPH_HDR_LENGTH(ipha);

		sp = &espstack->esp_sadb.s_v4;
		af = AF_INET;
	}

	esph = (esph_t *)(data_mp->b_rptr + preamble);

	/* Since hash is common on inbound (SPI value), hash here. */
	bucket = INBOUND_BUCKET(sp, esph->esph_spi);
	mutex_enter(&bucket->isaf_lock);
	ipsa = ipsec_getassocbyspi(bucket, esph->esph_spi, src_ptr, dst_ptr,
	    af);
	mutex_exit(&bucket->isaf_lock);

	if (ipsa == NULL || ipsa->ipsa_state == IPSA_STATE_DEAD ||
	    ipsa->ipsa_state == IPSA_STATE_ACTIVE_ELSEWHERE) {
		/*  This is a loggable error!  AUDIT ME! */
		IP_ESP_BUMP_STAT(ipss, lookup_failure);
		IP_ESP_BUMP_STAT(ipss, in_discards);
		ipsecesp_in_assocfailure(data_mp, 0,
		    SL_ERROR | SL_CONSOLE | SL_WARN,
		    "ipsec_inbound_esp_sa: No association found for "
		    "spi 0x%x, dst addr %s\n",
		    esph->esph_spi, dst_ptr, af, ira);
		if (ipsa != NULL) {
			IPSA_REFRELE(ipsa);
		}
		return (NULL);
	}

	if (ipsa->ipsa_state == IPSA_STATE_LARVAL) {
		/* Not fully baked; swap the packet under a rock until then */

		data_mp = sadb_set_lpkt(ipsa, data_mp, ira);
		if (data_mp == NULL) {
			IPSA_REFRELE(ipsa);
			return (NULL);
		}
		/* Looks like the SA is no longer LARVAL. */
	}

	/* Are the IPsec fields initialized at all? */
	if (!(ira->ira_flags & IRAF_IPSEC_SECURE)) {
		ira->ira_ipsec_action = NULL;
		ira->ira_ipsec_ah_sa = NULL;
		ira->ira_ipsec_esp_sa = NULL;
	}

	/*
	 * Save a reference to the association so that it can
	 * be retrieved after execution. We free any AH SA reference
	 * already there (innermost SA "wins". The reference to
	 * the SA will also be used later when doing the policy checks.
	 */
	if (ira->ira_ipsec_esp_sa != NULL) {
		IPSA_REFRELE(ira->ira_ipsec_esp_sa);
	}
	ira->ira_flags |= IRAF_IPSEC_SECURE;
	ira->ira_ipsec_esp_sa = ipsa;

	*esphp = esph;
	return (data_mp);
}
